package com.copm.ifm.base.basic.util;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.copm.ifm.base.basic.constant.AuthConstants;
import com.copm.ifm.base.basic.exception.UnauthorizedException;
import com.copm.ifm.common.redis.RedisConstants;
import com.copm.ifm.common.redis.RedisKeyHelper;
import com.copm.ifm.common.redis.RedisUtil;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.Callable;

/**
 * 权限相关工具类
 *
 * @author zzf
 * @date 2020/7/16 15:09
 */
public class AuthUtil {

    /**
     * 国际化-简体中文
     */
    private static final int LAN_CN = 1;

    /**
     * 国际化-繁体中文
     */
    private static final int LAN_FT = 2;

    /**
     * 国际化-英语
     */
    private static final int LAN_EN = 3;

    /**
     * 用户权限本地缓存
     */
    private static final Cache<Integer, Map<String, List<String>>> USER_MENU_MAP =
            Caffeine.newBuilder()
                    //最后访问时间超过一天就失效
                    .expireAfterAccess(Duration.ofDays(1))
                    .build();


    /**
     * 生成token
     *
     * @param staffId  用户id
     * @param language 用户默认语言
     */
    public static String generateToken(Integer staffId, Integer language, Integer tenantId, boolean rememberMe) {
        String sign = JWT.create()
                .withClaim(AuthConstants.TOKEN_DETAIL_ID, staffId)
                .withClaim(AuthConstants.TOKEN_DETAIL_LANGUAGE, language)
                .withClaim(AuthConstants.TOKEN_DETAIL_TENANT, tenantId)
                .sign(Algorithm.HMAC256(String.valueOf(System.currentTimeMillis())));
        //保存到redis
        setUserToken(staffId, sign, rememberMe);
        return sign;
    }

    /**
     * 保存用户token到redis
     */
    public static void setUserToken(Integer staffId, String token, boolean rememberMe) {
        if (staffId == null || token == null) {
            return;
        }
        if (rememberMe) {
            RedisUtil.getInstance().set(RedisKeyHelper.token(staffId), token, RedisConstants.DEFAULT_EXPIRE_SECOND * 15);
        } else {
            RedisUtil.getInstance().set(RedisKeyHelper.token(staffId), token);
        }
    }


    /**
     * 获取当前用户id
     */
    public static Integer getCurrentUserId() {
        DecodedJWT decode = getDecodedToken();
        return decode.getClaim(AuthConstants.TOKEN_DETAIL_ID).asInt();
    }

    /**
     * 获取指定用户的token
     */
    public static String getUserToken(Integer staffId) {
        if (staffId == null) {
            return null;
        }
        return RedisUtil.getInstance().get(RedisKeyHelper.token(staffId));
    }

    /**
     * 获取指定用户权限列表
     */
    public static List<String> getUserMenuOptionList(Integer staffId, String menuName) {
        Object object = RedisUtil.getInstance().hGetField(staffId.toString(), menuName);
        if (object == null) {
            return null;
        }
        return JSONObject.parseArray(JSON.toJSONString(object), String.class);
    }

    /**
     * 获取指定用户权限列表
     */
    public static List<String> getCurrentUserRole() {
        return Arrays.asList("asdf", "asdff");
    }


    /**
     * 获取当前用户权限
     */
    public static boolean checkRole(String menuName, String optionName) {
        Integer staffId = getCurrentUserId();
        //查看本地缓存
        if (checkLocal(staffId, menuName, optionName)) {
            return true;
        }
        //redis中查询
        List<String> optionList = getUserMenuOptionList(staffId, menuName);
        if (optionList != null) {
            if (optionList.contains(optionName)) {
                cacheLocal(staffId, menuName, optionName);
                return true;
            }
        }
        return false;
    }

    /**
     * 缓存权限信息到本地
     */
    private static void cacheLocal(Integer staffId, String menuName, String optionName) {
        Map<String, List<String>> menuMap = USER_MENU_MAP.getIfPresent(staffId);
        if (menuMap == null) {
            menuMap = new HashMap<>();
        }
        List<String> optionList = menuMap.get(menuName);
        if (optionList == null) {
            optionList = new ArrayList<>();
        }
        optionList.add(optionName);
        menuMap.put(menuName, optionList);
        USER_MENU_MAP.put(staffId, menuMap);
    }

    /**
     * 检查本地缓存中是否存在
     */
    private static boolean checkLocal(Integer staffId, String menuName, String optionName) {
        Map<String, List<String>> menuMap = USER_MENU_MAP.getIfPresent(staffId);
        if (menuMap == null) {
            return false;
        }
        List<String> optionNameList = menuMap.get(menuName);
        return optionNameList != null && optionNameList.contains(optionName);
    }

    /**
     * 清空指定用户的权限缓存
     */
    public static void clearLocalCache(Integer staffId) {
        USER_MENU_MAP.invalidate(staffId);
    }

    /**
     * 清空指定用户的权限缓存
     */
    public static void clearLocalCache(List<Integer> staffIdList) {
        USER_MENU_MAP.invalidateAll(staffIdList);
    }


    /**
     * 清空指定用户的权限缓存
     */
    public static void clearRedisCache(Integer staffId) {

        RedisUtil.getInstance().del(RedisKeyHelper.role(staffId));
    }

    /**
     * 清空本地所有用户权限缓存
     */
    public static void clearAllUserRoleLocalCache() {
        USER_MENU_MAP.cleanUp();
    }

    /**
     * 获取当前用户默认语言
     */
    public static Integer getCurrentUserLanguage() {
        DecodedJWT decode = getDecodedToken();
        return decode.getClaim(AuthConstants.TOKEN_DETAIL_LANGUAGE).asInt();
    }

    /**
     * 获取当前用户所在的租户
     */
    public static Integer getCurrentUserTenant() {
        DecodedJWT decode = getDecodedToken();
        return decode.getClaim(AuthConstants.TOKEN_DETAIL_TENANT).asInt();
    }


    /**
     * 多语言处理
     * <p/>
     * 这里使用了JDK8中的特性：行为参数化。
     *
     * @param function4CN 如果是语言是中文，需要执行的方法
     * @param function4FT 如果是语言是繁体，需要执行的方法
     * @param function4EN 如果是语言是英文，需要执行的方法
     * @param <T>         泛型
     */
    public static <T> T invokeByLanguage(Callable<T> function4CN,
                                         Callable<T> function4FT,
                                         Callable<T> function4EN) {
        return invokeByLanguage(getCurrentUserLanguage(), function4CN, function4FT, function4EN);
    }


    /**
     * 多语言处理
     * <p/>
     * 这里使用了JDK8中的特性：行为参数化。
     *
     * @param language    语言
     * @param function4CN 如果是语言是中文，需要执行的方法
     * @param function4FT 如果是语言是繁体，需要执行的方法
     * @param function4EN 如果是语言是英文，需要执行的方法
     * @param <T>         泛型
     */
    public static <T> T invokeByLanguage(int language,
                                         Callable<T> function4CN,
                                         Callable<T> function4FT,
                                         Callable<T> function4EN) {
        try {
            switch (language) {
                case LAN_FT: {
                    return function4FT.call();
                }
                case LAN_EN: {
                    return function4EN.call();
                }
                default: {
                    return function4CN.call();
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("多语言转换失败！");
        }
    }


    /**
     * 获取解密后的token信息
     */
    private static DecodedJWT getDecodedToken() {
        return JWT.decode(getCurrentToken());
    }

    public static String getCurrentToken() {
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();
        String token = request.getHeader(AuthConstants.TOKEN_NAME);
        if (StringUtils.isEmpty(token)) {
            throw new UnauthorizedException();
        }
        return token;
    }


    public static String getTokenByUserId(Integer userId) {
        return RedisUtil.getInstance().get(RedisKeyHelper.token(userId));
    }
}
